/*
 * Aritmetico.cpp
 *
 *  Created on: Nov 20, 2009
 *      Author: mmmary
 */
#include <iostream>
#include <cmath>
#include <vector>
#include <cstring>
#include "../Constantes.h"
#include "Aritmetico.h"
#include <cmath>
#include <algorithm>
#include <iostream>

Aritmetico::Aritmetico() {

	contextoActual = NULL;
	contextoNulo = true;
	buffer = 0;
	bitsEnBuff = 0;
	contadorUnderflow = 0;
	techo = 0;
	piso = 0;

}

Aritmetico::~Aritmetico() {

}

void Aritmetico::resetContadorUnderflow() {
	this->contadorUnderflow = 0;
}

uint32 Aritmetico::getPiso() {
	return piso;
}
uint32 Aritmetico::getTecho() {
	return techo;
}
void Aritmetico::setPiso(uint32 valor) {
	this->piso = valor;
}
void Aritmetico::setTecho(uint32 valor) {
	this->techo = valor;
}

void Aritmetico::CalcularPisoTecho(char& simbolo, uint32& pisoSimbolo,
		uint32& techoSimbolo, CompreODescompre idaOVuelta, uint32 valorSimbolo) {
	char temporal = 0;
	bool encontrado = false;
	float prob = 0;
	uint32 longitud;
	uint32 TECHO_MAX = (pow(2, PRECISION) - 2);

	if (contextoNulo) {

		uint32 techoItervalo = TECHO_MAX;
		longitud = techoItervalo + 1;
		prob = floor(((float) longitud / TAMANIO_DICCIONARIO));
		techoSimbolo = -1;
	} else {

		if (this->getTecho() > TECHO_MAX) {
			this->setTecho(TECHO_MAX);
		}
		longitud = this->getTecho() - this->getPiso() + 1;
		techoSimbolo = this->getPiso() - 1;
	}

	do {
		if (contextoNulo) {
			pisoSimbolo = techoSimbolo + 1;
			techoSimbolo = (pisoSimbolo + prob - 1);

		} else {
			pisoSimbolo = techoSimbolo + 1;
			/*busco la probabilidad de ese caracter*/
			prob = contextoActual->getProbabilidad(temporal);
			techoSimbolo = floor(pisoSimbolo + ((float) longitud * prob)) - 1;

		}

		if ((idaOVuelta == COMPRIMIENDO) && (simbolo == temporal)) {
			encontrado = true;
		}

		if ((idaOVuelta == DESCOMPRIMIENDO) && (valorSimbolo >= pisoSimbolo)
				&& (valorSimbolo <= techoSimbolo)) {
			encontrado = true;
			simbolo = temporal;
			return;
		}
		temporal++;
	} while (!encontrado);
}

/*agrego unos al final*/
void Aritmetico::nuevoTechoOver(uint32& techoSimbolo, char cantALeer) {

	techoSimbolo <<= cantALeer;
	uint32 unos = 0;
	unos = pow(2, cantALeer) - 1;
	techoSimbolo |= unos;

}

/*agrego ceros al final*/
void Aritmetico::nuevoPisoOver(uint32& pisoSimbolo, char cantALeer) {
	pisoSimbolo <<= cantALeer;
}

void Aritmetico::nuevoTechoPisoOver(uint32& techoSimbolo, uint32& pisoSimbolo,
		char cantALeer) {
	nuevoTechoOver(techoSimbolo, cantALeer);
	nuevoPisoOver(pisoSimbolo, cantALeer);

}

void Aritmetico::nuevoTechoUnder(uint32& techoSimbolo, char cantALeer) {
	/*tengo algo de la forma 1-0....0-resto, donde tengo en resto, unos y ceros*/
	/*y me quedo con 1-resto-1...1*/
	techoSimbolo <<= cantALeer;
	techoSimbolo |= BIT32;
	uint32 unos = 0;
	unos = pow(2, cantALeer) - 1;
	techoSimbolo |= unos;

}

void Aritmetico::nuevoPisoUnder(uint32& pisoSimbolo, char cantALeer) {
	/*tengo algo de la forma 0-1....1-resto, donde tengo en resto, unos y ceros*/
	/*y me quedo con 0-resto-0...0*/

	pisoSimbolo <<= (cantALeer);
	pisoSimbolo &= (~BIT32);
}
void Aritmetico::nuevoTechoPisoUnder(uint32& techoSimbolo, uint32& pisoSimbolo,
		char cantALeer) {
	nuevoTechoUnder(techoSimbolo, cantALeer);
	nuevoPisoUnder(pisoSimbolo, cantALeer);

}

void Aritmetico::agregarContexto(char simbolo) {
	if (contextoNulo == true) {
		Contexto contexto(simbolo);
		contextos.push_back(contexto);
		actualizarContexto(0);

	} else {

		Contexto contextoNuevo(simbolo);
		contextos.push_back(contextoNuevo);
		sort(contextos.begin(), contextos.end());
		for (unsigned i = 0; i < contextos.size(); i++) {
			if (contextos[i].getValorAscii() == simbolo) {
				actualizarContexto(i);
			}

		}

	}

}

void Aritmetico::actualizarContexto(int posicion) {

	contextoActual = &contextos[posicion];
	if (contextoNulo == true) {
		contextoNulo = false;
	}

}

void Aritmetico::procesarContexto(char simbolo) {

	bool encontrado = false;
	bool mePase = false;
	unsigned i = 0;
	/*busco el nuevo contexto y le asigno los nuevos piso y techo*/
	/*si no lo encuentro, lo agrego a la lista*/

	if (contextoNulo == true) {
		agregarContexto(simbolo);
	} else {

		while ((i < contextos.size()) && (!encontrado) && (!mePase)) {
			if (contextos[i].getValorAscii() < simbolo) {
				i++;
			} else {
				if (contextos[i].getValorAscii() == simbolo) {
					encontrado = true;
				} else {
					mePase = true;
				}
			}
		}

		if (encontrado) {
			actualizarContexto(i);
		}

		if (!encontrado) {
			/*no esta en la tabla*/
			agregarContexto(simbolo);
		}
	}
}

void Aritmetico::procesarSimbolo(char simbolo) {

	uint32 pisoSimbolo = 0;
	uint32 techoSimbolo = 0;
	uint32 noSeUsa;
	CalcularPisoTecho(simbolo, pisoSimbolo, techoSimbolo, COMPRIMIENDO, noSeUsa);
	uint32 salida;

	char cantALeer = overflow(techoSimbolo, pisoSimbolo, salida);
	if (cantALeer != 0) {
		emitirOver(cantALeer, salida);
		nuevoTechoPisoOver(techoSimbolo, pisoSimbolo, cantALeer);
	}

	char cantUnder = underflow(techoSimbolo, pisoSimbolo);
	if (cantUnder != 0) {
		if (contextoNulo != true) {
			contadorUnderflow += cantUnder;
		}
		nuevoTechoPisoUnder(techoSimbolo, pisoSimbolo, cantUnder);
	}

	if (this->contextoNulo != true) {
		this->contextoActual->actualizarFrecuencia(simbolo);
		this->contextoNulo = false;
	}

	this->setPiso(pisoSimbolo);
	this->setTecho(techoSimbolo);
	procesarContexto(simbolo);
}
uint32 cambiarOrden(uint32 byte) {

	return (((0x000000ff & byte) << 24) | ((0xff000000 & byte) >> 24)
			| ((0x00ff0000 & byte) >> 8) | ((0x0000ff00 & byte) << 8));

}

uint32 Aritmetico::leerNbits(Bytes& bytes, char N, char& bitN, cantBytes& byteN) {
	uint32 temp = 0;
	char byte = 0;
	bytes.leer(&byte, byteN, 1);
	while (N != 0) {
		if (bitN == 8) {
			bitN = 0;
			byteN++;
			bytes.leer(&byte, byteN, 1);
		}
		temp |= (((int) pow(2, 7 - bitN) & (int) byte) >> (7 - bitN));
		temp <<= 1;
		N--;
		bitN++;
	}
	temp >>= 1;
	return temp;
}
void Aritmetico::moverVentanaNbits(uint32& ventanaBits, char N, Bytes &bytes,
		cantBytes& contadorBytes, char& bitN, casoDesborde tipoDesborde) {
	ventanaBits = cambiarOrden(ventanaBits);
	uint32 temp = leerNbits(bytes, N, bitN, contadorBytes);
	if (tipoDesborde == CASO_OVERFLOW) {
		ventanaBits <<= N;
		ventanaBits |= temp;
	} else {
		uint32 primerBit = ((ventanaBits & BIT32));
		ventanaBits <<= N;
		if (primerBit == 0) {
			ventanaBits &= 0x7FFFFFFF;
		} else {
			ventanaBits |= 0x80000000;
		}
		ventanaBits |= temp;
	}
	ventanaBits = cambiarOrden(ventanaBits);
}

void Aritmetico::inicializarVentana(Bytes& bytes, uint32& ventanaBits) {
	cantBytes tamanio = bytes.getTamanio();
	char byteNulo = 0;
	unsigned size = PRECISION / 8;
	if (tamanio < size) {
		int diferencia = size - tamanio;
		for (int i = 0; i < diferencia; i++) {
			bytes.agregarAlFinal(&byteNulo, 1);
		}
	}
	bytes.leer(&ventanaBits, 0, size);

}

bool Aritmetico::posibleEmisionFinal(uint32 pisoSimbolo,
		uint32 valorADecodificar) {

	return pisoSimbolo == valorADecodificar;

}

void Aritmetico::sacarRelleno(Bytes& bytes) {
	char c = 0;
	bool continuar = false;
	cantBytes tamanio = 0;
	do {
		tamanio = bytes.getTamanio();
		bytes.leer(&c, tamanio - 1, 1);
		if (c == 0) {
			bytes = bytes.getSubBytes(0, tamanio - 1);
			continuar = true;

		} else {
			continuar = false;
		}
	} while (continuar);
}

Bytes Aritmetico::descomprimir(Bytes bytes) {
	reset();
	sacarRelleno(bytes);
	uint32 ventanaBits = 0;
	char bitN = 8;
	uint32 pisoSimbolo = 0;
	uint32 techoSimbolo = 0;
	uint32 noseUsa = 0;
	bool ultimaEmision = false;
	inicializarVentana(bytes, ventanaBits);
	cantBytes tamanio = bytes.getTamanio();
	cantBytes contadorBytes = PRECISION / 8 - 1;
	bool termineLectura = false;
	uint32 valorADecodificar = 0;
	while (!termineLectura) {

		char simboloAEmitir;

		valorADecodificar = cambiarOrden(ventanaBits);

		CalcularPisoTecho(simboloAEmitir, pisoSimbolo, techoSimbolo,
				DESCOMPRIMIENDO, valorADecodificar);

		salida.agregarAlFinal(&simboloAEmitir, sizeof(char));//guardar ese simbolo en un buffer de la clase

		ultimaEmision = posibleEmisionFinal(pisoSimbolo, valorADecodificar);
		char cantALeer = overflow(techoSimbolo, pisoSimbolo, noseUsa);
		if (cantALeer != 0) {

			moverVentanaNbits(ventanaBits, cantALeer, bytes, contadorBytes,
					bitN, CASO_OVERFLOW);
			nuevoTechoPisoOver(techoSimbolo, pisoSimbolo, cantALeer);
		}

		char cantidadU = underflow(techoSimbolo, pisoSimbolo);
		if (cantidadU != 0) {

			moverVentanaNbits(ventanaBits, cantidadU, bytes, contadorBytes,
					bitN, CASO_UNDERFLOW);

			nuevoTechoPisoUnder(techoSimbolo, pisoSimbolo, cantidadU);
		}

		if (this->contextoNulo != true) {
			this->contextoActual->actualizarFrecuencia(simboloAEmitir);
			this->contextoNulo = false;
		}

		if ((ultimaEmision == true) && (estoyEnElPadding(bitN, contadorBytes,
				bytes))) {
			termineLectura = true;
		} else {

			this->setPiso(pisoSimbolo);
			this->setTecho(techoSimbolo);
			procesarContexto(simboloAEmitir);
		}
	}
	return this->salida;
}

/*Tabla de verdad ~(t | (~p)) :
 * t p res
 * 0 0  0
 * 0 1  1
 * 1 0  0
 * 1 1  0
 */
char Aritmetico::underflow(uint32 t, uint32 p) {
	uint32 res = 0;
	if ((t & BIT32) && ((p & BIT32) == 0)) {
		t <<= 1;
		p <<= 1;
		res = ~(t | (~p));
		char contador = 0;
		while (res & BIT32) {
			res <<= 1;
			contador++;
		}
		return contador;
	}
	return 0;
}
/*
 * t  100101000
 * p  100001000
 * r  111011111
 * s  100000000
 */
char Aritmetico::overflow(uint32 t, uint32 p, uint32 &s) {
	uint32 res = ~(t ^ p);
	char contador = 0;
	while (res & BIT32) {
		res <<= 1;
		contador++;
	}
	s = ((t >> (PRECISION - contador)) << (PRECISION - contador));
	return contador;
}

void Aritmetico::pushBit(char bit) {
	bit &= 0x01;
	buffer <<= 1;
	buffer |= bit;
	bitsEnBuff++;
	if (bitsEnBuff == (sizeof(char) * 8)) {
		this->salida.agregarAlFinal(&(this->buffer), sizeof(char));
		this->buffer = 0;
		this->bitsEnBuff = 0;

	}
}

void Aritmetico::emitirOver(char cantBitsOver, uint32 bitsSalida) {
	char primerBit = ((bitsSalida & BIT32) >> 31); // 0 o 1
	/*meto el primer bit */
	this->pushBit(primerBit);
	cantBitsOver--;
	/* borro el primer bit */
	bitsSalida <<= 1;

	/*Meto U veces el ~primerBit */
	int u = 0;
	if (this->contextoActual != NULL) {
		u = this->contadorUnderflow;
		while (u--) {
			this->pushBit(~primerBit);
		}
		this->resetContadorUnderflow();
	}

	/*Meto el resto de lo bits del overflow*/
	while (cantBitsOver--) {
		this->pushBit(((bitsSalida & BIT32) >> 31));
		bitsSalida <<= 1;
	}
}

void Aritmetico::agregarPadding() {

	if (this->bitsEnBuff == 0) {
		this->buffer = 0x80;
	} else {
		this->buffer <<= 1;
		this->buffer |= 1;
		this->bitsEnBuff++;
		this->buffer <<= (8 - bitsEnBuff);
	}
	salida.agregarAlFinal(&(this->buffer), sizeof(char));
}

Bytes Aritmetico::getSalida() {
	return salida;
}

void Aritmetico::emitirUltimoPiso() {
	uint32 piso = this->getPiso();
	emitirOver(32, piso);
}

void Aritmetico::reset() {
	contextoActual = NULL;
	contextoNulo = true;
	contadorUnderflow = 0;
	piso = 0;
	techo = 0;
	buffer = 0;
	bitsEnBuff = 0;
	contadorUnderflow = 0;
	contextos.clear();
	salida.vaciar();
}

bool Aritmetico::estoyEnElPadding(char bitN, cantBytes byteN,
		const Bytes& bytes) {
	cantBytes tam = bytes.getTamanio();
	char aux = 0;
	char mask = 0x80;
	if (byteN == (tam - 1)) {
		bytes.leer(&aux, byteN, 1);
		aux <<= bitN;
		aux >>= bitN;
		mask = pow(2, 8 - bitN);
		return (mask & aux);
	}
	if (byteN == (tam - 2)) {
		bytes.leer(&aux, byteN + 1, 1);
		return ((bitN == 8) && (mask & aux));
	}
	return false;
}

Bytes Aritmetico::comprimir(Bytes bytes) {
	reset();
	offset tam = bytes.getTamanio();
	for (offset i = 0; i < tam; ++i) {
		procesarSimbolo(bytes[i]);
	}
	emitirUltimoPiso();
	agregarPadding();
	Bytes salidaAEmitir = this->salida;
	reset();
	return salidaAEmitir;
}

void Aritmetico::imprimirContextos() {

	std::cout << "-----------IMPRIMO TODOS LOS CONTEXTOS----------"
			<< std::endl;
	for (unsigned j = 0; j < contextos.size(); j++) {
		contextos[j].imprimir();
	}
	std::cout << "------------------------------------------------"
			<< std::endl;
}
